package game.Actors;

import java.util.ArrayList;

import game.GameHud;
import game.LevelManager;
import game.Config.Config;
import game.Config.Storage;
import game.Config.TowerConfig;
import game.Config.TowerInfo;

import org.newdawn.slick.Color;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.geom.Vector2f;
import org.newdawn.slick.tiled.TiledMap;

import Weapons.Weapon;
/**
 * This class represents the player. The player class
 * extends MovableActor, and thus moves through the screen when 
 * updated.
 * @author Sebastian Isheden and Victor Beer
 *
 */
public class Player extends MovableActor{
	private ArrayList<Weapon> weapons = new ArrayList<Weapon>();
	private int currentWeapon = 0;
	private Vector2f shootDirection; 
	private int money = Config.PLAYER_START_GOLD;
	/**
	 * Creates a Player with given bulletImage.
	 * @param bulletImage	The image of the bullet the player shoots.
	 */
	public Player(Image image,Vector2f position,Weapon weapon) {
		super(image, position);
		weapons.add(weapon);
	}
	/**
	 * Moves the player in the given velocity.
	 * @param velocity	The velocity of the player.
	 */
	public void move(Vector2f velocity){
		this.setSpeed(velocity.length());
		this.setDirection(velocity);
	}

	/**
	 * Sets the shootDirection of the player and rotates the image accordingly.
	 * @param mousePosition The position of the mouse.
	 */
	public void setShootDirection(Vector2f mousePosition){
		this.shootDirection = new Vector2f(mousePosition.x - this.position.x,mousePosition.y - this.position.y).normalise();
		this.setImageRotation();
	}
	
	@Override
	protected void setImageRotation(){
		this.image.setRotation((float)shootDirection.getTheta());
	}
	/**
	 * Sets the shootDirection of the player and rotates the image accordingly.
	 * @param x	The x coordinate of the mouse.
	 * @param y The y coordinate of the mouse.
	 */
	public void setShootDirection(int x, int y){
		this.setShootDirection(new Vector2f(x,y));
	}	
	/**
	 * Makes the player shoot with the weapon currently in use.
	 * The weapon in use can be changed with the method swapWeapon.
	 */
	public void shoot(){
		Vector2f shootOffset = new Vector2f(this.image.getWidth()/2,0);
		shootOffset.setTheta(this.shootDirection.getTheta());
		this.weapons.get(currentWeapon).fire(this.position.copy().add(shootOffset), this.shootDirection);  
	}
	
	/**
	 * @param weapon The weapon we want to add to the players arsenal.
	 */
	public void addWeapon(Weapon weapon) {
		this.weapons.add(weapon);
	}
	/**
	 * Equips the player with the next Weapon in his arsenal.
	 */
	public void swapWeapon() {
		this.currentWeapon = (this.currentWeapon + 1) % weapons.size();
		GameHud.setActiveWeapon(this.currentWeapon);
	}
	/**
	 * Builds a tower on the screen. The tower can only be built
	 * if the player has enough gold to afford the tower.
	 * The tower will then be built based on a given towerInfo.
	 * @param info The information about the tower we want to build. 
	 * @return True - If a tower was built. False - If the amount of money wasn't enough.
	 */
	public boolean buildTower(TowerInfo info){
		if(info.getCost() > this.money){
			return false;
		}
		Vector2f tilePosition = this.position.copy().add(shootDirection.copy().scale(Config.TILE_PIXEL_AMOUNT)).scale(1/Config.TILE_PIXEL_AMOUNT);
		TiledMap tileMap = LevelManager.getCurrentLevel().getTileMap();
		tileMap.setTileId((int)tilePosition.x, (int)tilePosition.y,Config.COLLISION_LAYER_ID, Config.UNBUILDABLE_TILE_ID);
		Vector2f towerPosition = new Vector2f((int)tilePosition.x,(int)tilePosition.y).scale(Config.TILE_PIXEL_AMOUNT).add(new Vector2f(Config.TILE_PIXEL_AMOUNT/2,Config.TILE_PIXEL_AMOUNT/2));
		TowerConfig.towerInfoMap.get(info).placeTower(towerPosition);
		this.money -= info.getCost();
		return true;
		
	}
	@Override
	protected void update(int delta){
		this.weapons.get(currentWeapon).update(delta);
		
		this.updateAndCheckCollision(delta);
		this.checkScreenBoundaries();
	}
	
	/**
	 * Updates the player position and checks the collision for all the impassable objects in the level.
	 */
	private void updateAndCheckCollision(int delta){
		TiledMap tileMap = LevelManager.getCurrentLevel().getTileMap();
		
		Vector2f tilePosition = this.position.copy().scale(1/Config.TILE_PIXEL_AMOUNT);
		
		//Calculate all the possible tiles the character can be colliding with
		int tileXMin = Math.max(0, (int)tilePosition.x - 1);
		int tileXMax = Math.min(tileMap.getWidth() - 1, (int)tilePosition.x + 1);
		int tileYMin = Math.max(0, (int)tilePosition.y - 1);
		int tileYMax = Math.min(tileMap.getHeight() - 1, (int)tilePosition.y + 1);
		Rectangle thisHitRectangle = null;
		ArrayList<Rectangle> hitRectangles = new ArrayList<Rectangle>();
		
		for(int x = tileXMin; x <= tileXMax; x++) {
			for(int y = tileYMin; y <= tileYMax; y++) {
				if(tileMap.getTileId(x, y, Config.COLLISION_LAYER_ID) == Config.IMPASSABLE_TILE_ID) {
					hitRectangles.add(createTileRectangle(x,y));
				}
			}
		}
		
		this.position.add(new Vector2f(this.direction.x * this.speed*delta/Config.MILLISECONDS, 0));
		thisHitRectangle = this.getCollisionRectangle(Config.PLAYER_COLLISION_PERCENT);
		for(Rectangle rect : hitRectangles) {
			if(thisHitRectangle.intersects(rect)) {
				this.position.add(new Vector2f(this.direction.x * -this.speed*delta/Config.MILLISECONDS, 0));
				break;
			}
		}
		
		this.position.add(new Vector2f(0, this.direction.y * this.speed*delta/Config.MILLISECONDS));
		thisHitRectangle = this.getCollisionRectangle(Config.PLAYER_COLLISION_PERCENT);
		for(Rectangle rect : hitRectangles) {
			if(thisHitRectangle.intersects(rect)) {
				this.position.add(new Vector2f(0, this.direction.y * -this.speed*delta/Config.MILLISECONDS));
				break;
			}
		}
	}
	@Override
	protected void draw(){
		super.draw();
		drawBuildRectangle(new Graphics());
	}
	private void drawBuildRectangle(Graphics graphic) {
		TiledMap map = LevelManager.getCurrentLevel().getTileMap();
		Vector2f tilePosition = this.position.copy().add(shootDirection.copy().scale(Config.TILE_PIXEL_AMOUNT)).scale(1/Config.TILE_PIXEL_AMOUNT);
		
		if(tilePosition.x <= 0 || tilePosition.x >= map.getWidth()) {
			return;
		}
		if(tilePosition.y <= 0 || tilePosition.y >= map.getHeight()) {
			return;
		}
		
		graphic.setColor(Color.green);
		Rectangle rectangle = new Rectangle((int)(tilePosition.x)*Config.TILE_PIXEL_AMOUNT,(int)(tilePosition.y)*Config.TILE_PIXEL_AMOUNT,Config.TILE_PIXEL_AMOUNT,Config.TILE_PIXEL_AMOUNT);
		if(map.getTileId((int)tilePosition.x, (int)tilePosition.y, Config.COLLISION_LAYER_ID) == Config.UNBUILDABLE_TILE_ID){
			graphic.setColor(Color.red);
		}
		if(map.getTileId((int)tilePosition.x, (int)tilePosition.y, Config.COLLISION_LAYER_ID) == Config.IMPASSABLE_TILE_ID){
			return;
		}
		graphic.draw(rectangle);
	}
	private Rectangle createTileRectangle(int x, int y) {
		return new Rectangle(x*Config.TILE_PIXEL_AMOUNT, y*Config.TILE_PIXEL_AMOUNT, Config.TILE_PIXEL_AMOUNT, Config.TILE_PIXEL_AMOUNT);
	}

	private void checkScreenBoundaries(){
		if(this.position.x < this.image.getWidth()/2){
			this.position.x = this.image.getWidth()/2;
		}else if(this.position.x + this.image.getWidth()/2 > Config.SCREEN_WIDTH){
			this.position.x = Config.SCREEN_WIDTH - this.image.getWidth()/2;
		}
		if(this.position.y < this.image.getHeight()/2){
			this.position.y = this.image.getHeight()/2;
		}else if(this.position.y + this.image.getHeight()/2 > Config.SCREEN_HEIGHT){
			this.position.y = Config.SCREEN_HEIGHT - this.image.getHeight()/2;
		}	
	}
	/**
	 * This method checks the tile just infront of the player(In the looking-direction of the player)
	 * and determines if the tile is buildable.
	 * @return	True - If the tile in front of the player is buildable. False - If the tile in front of the player is unbuildable or impassable.
	 */
	public boolean canBuild() {
		TiledMap map = LevelManager.getCurrentLevel().getTileMap();
		Vector2f tilePosition = this.position.copy().add(shootDirection.copy().scale(Config.TILE_PIXEL_AMOUNT)).scale(1/Config.TILE_PIXEL_AMOUNT);
		
		if(tilePosition.x <= 0 || tilePosition.x >= map.getWidth()) {
			return false;
		}
		if(tilePosition.y <= 0 || tilePosition.y >= map.getHeight()) {
			return false;
		}
		
		if(map.getTileId((int)tilePosition.x, (int)tilePosition.y, Config.COLLISION_LAYER_ID) == Config.UNBUILDABLE_TILE_ID ||
		   map.getTileId((int)tilePosition.x, (int)tilePosition.y, Config.COLLISION_LAYER_ID) == Config.IMPASSABLE_TILE_ID){
			return false;
		}
		Vector2f towerPosition = new Vector2f((int)tilePosition.x,(int)tilePosition.y).scale(Config.TILE_PIXEL_AMOUNT);
		Storage.storePosition(towerPosition);
		return true;
	}
	/**
	 * Gives the player money.
	 * @param money The amount of money given to the player.
	 */
	public void receiveMoney(float money) {
		this.money +=money;
		
	}
	/**
	 * @return The amount of money the player is carrying.
	 */
	public int getMoney(){
		return this.money;
	}
	/**
	 * This method checks the tile just in front of the player
	 * and determines if there are any towers on that tile.
	 * @return	True - If the tile contains a tower. False otherwise.
	 */
	public boolean canUpgrade() {
		Vector2f lookingPosition = this.position.copy().add(shootDirection.copy().scale(Config.TILE_PIXEL_AMOUNT));
		for(Tower tower : Tower.getTowerList()){
			if(tower.getCollisionRectangle(1f).contains(lookingPosition.x, lookingPosition.y)){
				Storage.storeTower(tower);
				Storage.storePosition(tower.position.copy().add(new Vector2f(-tower.image.getWidth()/2,-tower.image.getHeight()/2)));
				return true;
			}
		}
		return false;
	}
}
